package org.bookie.framework.components;

import static org.bookie.core.services.impl.Listener.Event.USER_LOGGED_IN;

import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.IActionListener;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.annotations.InjectObject;
import org.apache.tapestry.annotations.InjectState;
import org.bookie.core.services.impl.Listener;
import org.bookie.core.services.impl.Listener.Event;
import org.bookie.framework.CommonBaseComponent;
import org.bookie.framework.services.MessageManager;
import org.bookie.model.Bet;
import org.bookie.model.BetSlip;
import org.bookie.model.MultipleBetSlip;
import org.bookie.model.Outcome;
import org.bookie.model.SingleBetSlip;
import org.bookie.model.Wager;
import org.bookie.model.Event.State;
import org.bookie.model.services.BetSlipDAO;

/**
 * Bet slip component provides the functionality to construct and store a {@link BetSlip}.
 * It contains a tabbed pane to switch between different bet slip types and to store 
 * the selected {@link Wager}s into the current {@link BetSlip}. Futhermore it contains
 * a submission form and a confirmation dialog which summarises the {@link BetSlip}'s data.
 * 
 * @author Igor Drobiazko
 * @author Renat Zubairov
 * 
 */
public abstract class BetSlipComponent extends CommonBaseComponent implements
		ITabPanelEventListener, IBetSlipComponent {
	public static final String CONFIRMATION_DIALOG_ID = "confirmationDialog";

	/**
	 * Identifier of the 'single' pane of the tabbed pane
	 */
	public static final String TAB_SINGLE = "single";

	/**
	 * Identifier of the tabbed pane component inside BetSlip component
	 */
	public static final String TABS_COMPONENT_ID = "tabs";

	
	private static final Log log = LogFactory.getLog(BetSlipComponent.class);
	
	/**
	 * Get injected {@link BetSlip}
	 */
	@InjectState("betSlip")
	public abstract BetSlip getBetSlip();
	
	/**
	 * Set a {@link BetSlip} into component
	 * @param betSlip BetSlip to set
	 */
	public abstract void setBetSlip(BetSlip betSlip);

	/**
	 * Property
	 * @return
	 */
	public abstract void setConfirmShown(boolean value);

	/**
	 * Parameter that holds a listener to be triggered when login needed
	 * @return instance of IActionListener
	 */
	public abstract IActionListener getLoginListener();
	
	/**
	 * Injected BetSlipDAO
	 * @return instance of BetSlipDAO
	 */
	@InjectObject("service:bookie.model.BetSlipDAO")
	public abstract BetSlipDAO getBetSlipDAO();
	
	@InjectObject("service:bookie.framework.MessageManager")
	public abstract MessageManager getMessageManager();

	
	/**
	 * Places the bet
	 * 
	 * @return
	 */
	public void placeBet(IRequestCycle cycle) {
		getResponseBuilder().updateComponent("betSlipErrors");
		if (getBetSlip().getBets().size() < 1) {
			recordError("form.no.outcome");
		}		
		if (getDelegate().getHasErrors()) {
			return;
		}
		if (!getAuthenticationContext().isUserAuthenticated()) {
			getLoginListener().actionTriggered(this, cycle);
			return;
		}
		if (!validate()) {
			return;
		}
		setConfirmShown(true);
		updateComponents(CONFIRMATION_DIALOG_ID);
	}

	/**
	 * Validates the betslip
	 * 
	 * @return
	 */
	private boolean validate() {
		Double balance = getAuthenticationContext().getUser().getAccount().getBalance();
		if (balance.compareTo(getBetSlip().getStake()) < 0) {
			recordError("form.balance");
			return false;
		}
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public void addOutcome(Outcome outcome) {
		if(!isSelectable(outcome))return;
		if (findBet(outcome.getId()) == null) {
			getBetSlip().addBet(new Wager(outcome));
			updateMe();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isSelected(Outcome outcome) {
		if(!isSelectable(outcome)) return true;
		for(Wager bet: getBetSlip().getBets()){
			if(bet.getOutcome().equals(outcome))
				return true;
		}
		return false;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public boolean isSelectable(Outcome outcome){
        Bet bet = outcome.getBet();
        if(bet.getSuccessfulOutcome()!=null)
            return false;
        if(!bet.getEvent().getState().equals( State.ACTIVE))
            return false;
        return true;
	}

	/**
	 * Updates the component to the client
	 */
	private void updateMe() {
		updateComponents(TABS_COMPONENT_ID);
	}

	/**
	 * Deletes entry by ID
	 * 
	 * @param id
	 */
	public void deleteEntry(Long id) {
		Wager bet = findBet(id);
		if (null != bet) {
			getBetSlip().getBets().remove(bet);
			updateMe();
			getNotificationService().notifyListeners(Event.BET_REMOVED);
		}
	}

	/**
	 * Finds the bet with given ID in the bets
	 * 
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Wager findBet(Long id) {
		Iterator iter = getBetSlip().getBets().iterator();
		while (iter.hasNext()) {
			Wager bet = (Wager) iter.next();
			if (bet.getOutcome().getId().equals(id)) {
				return bet;
			}
		}
		return null;
	}

	/**
	 * Called by the internal tabbed pane when new tab is selected
	 * 
	 */
	public void tabSelected(String selectedId) {
		log.debug("Notification arrived!!! " + selectedId);
		BetSlip betSlip = getBetSlip();
		if (TAB_SINGLE.equals(selectedId)) {
			if (betSlip instanceof MultipleBetSlip) {
				setBetSlip(new SingleBetSlip(betSlip));
			}
		} else {
			if (betSlip instanceof SingleBetSlip) {
				setBetSlip(new MultipleBetSlip(betSlip));
			}
		}
	}

	
	public void cancelConfirm() {
		setConfirmShown(false);
		updateComponents(CONFIRMATION_DIALOG_ID);
	}
	
	public void confirm() {
		cancelConfirm();
		BetSlip betSlip = getBetSlip();
		betSlip.setUser(getAuthenticationContext().getUser());
		getBetSlipDAO().makePersistent(betSlip);
		getMessageManager().betSlipPlaced(betSlip);
		
		setBetSlip(new SingleBetSlip());
		getNotificationService().notifyListeners(Event.BET_SLIP_PLACED);
	}
	
	/**
	 * Listener method inherited from {@link ILoginComponentListener}
	 * triggered when user logged in (by login or registration)
	 */
	@Listener(USER_LOGGED_IN)
	public void userLoggedIn(String clientId) {
		if (getClientId().equals(clientId)) {
			log.debug("Login procedure triggered by the BetSlip completed, proceed with placeBet");
			placeBet(getRequestCycle());
		}
	}

}
